Move worktree bootstrap to the server and persist terminal launch context#1518
Move worktree bootstrap to the server and persist terminal launch context#1518juliusmarminge wants to merge 6 commits intomainfrom
Conversation
- keep terminal drawer attached to the created worktree for first-send draft setup scripts - flush local thread and draft state updates before opening terminals
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
- launch project setup scripts from the server after worktree prep - keep setup terminal state tied to the thread and surface activity events - add coverage for setup runner and bootstrap turn flow
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Stale "preparing-worktree" phase shows misleading UI state
- Removed the stale 'preparing-worktree' send phase, its derived
isPreparingWorktreeflag, and all UI references since worktree preparation now happens server-side and the phase no longer corresponds to any client-side work.
- Removed the stale 'preparing-worktree' send phase, its derived
Or push these changes by commenting:
@cursor push 88bafdb9f0
Preview (88bafdb9f0)
diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts
--- a/apps/web/src/components/ChatView.logic.ts
+++ b/apps/web/src/components/ChatView.logic.ts
@@ -75,7 +75,7 @@
return previewUrls;
}
-export type SendPhase = "idle" | "preparing-worktree" | "sending-turn";
+export type SendPhase = "idle" | "sending-turn";
export interface PullRequestDialogState {
initialReference: string | null;
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -658,7 +658,6 @@
const selectedModelForPicker = selectedModel;
const phase = derivePhase(activeThread?.session ?? null);
const isSendBusy = sendPhase !== "idle";
- const isPreparingWorktree = sendPhase === "preparing-worktree";
const isWorking = phase === "running" || isSendBusy || isConnecting || isRevertingCheckpoint;
const nowIso = new Date(nowTick).toISOString();
const activeWorkStartedAt = deriveActiveWorkStartedAt(
@@ -2573,7 +2572,7 @@
}
sendInFlightRef.current = true;
- beginSendPhase(baseBranchForWorktree ? "preparing-worktree" : "sending-turn");
+ beginSendPhase("sending-turn");
const composerImagesSnapshot = [...composerImages];
const composerTerminalContextsSnapshot = [...sendableComposerTerminalContexts];
@@ -4006,11 +4005,6 @@
{activeContextWindow ? (
<ContextWindowMeter usage={activeContextWindow} />
) : null}
- {isPreparingWorktree ? (
- <span className="text-muted-foreground/70 text-xs">
- Preparing worktree...
- </span>
- ) : null}
{activePendingProgress ? (
<div className="flex items-center gap-2">
{activePendingProgress.questionIndex > 0 ? (
@@ -4115,11 +4109,9 @@
aria-label={
isConnecting
? "Connecting"
- : isPreparingWorktree
- ? "Preparing worktree"
- : isSendBusy
- ? "Sending"
- : "Send message"
+ : isSendBusy
+ ? "Sending"
+ : "Send message"
}
>
{isConnecting || isSendBusy ? (Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
| return yield* orchestrationEngine.dispatch(finalTurnStartCommand); | ||
| }).pipe(Effect.mapError(toBootstrapRouteRequestError)); | ||
|
|
||
| return yield* bootstrapProgram.pipe( |
There was a problem hiding this comment.
🟢 Low src/wsServer.ts:787
When a defect (Die) occurs inside bootstrapProgram (e.g., a null dereference in git.createWorktree), Effect.catch fails to intercept it because Effect.catch only catches typed Fail errors via Cause.findError. The cleanupCreatedThread() handler never runs, leaving orphaned threads. Consider using Effect.catchCause to handle all failure modes including defects.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/wsServer.ts around line 787:
When a defect (`Die`) occurs inside `bootstrapProgram` (e.g., a null dereference in `git.createWorktree`), `Effect.catch` fails to intercept it because `Effect.catch` only catches typed `Fail` errors via `Cause.findError`. The `cleanupCreatedThread()` handler never runs, leaving orphaned threads. Consider using `Effect.catchCause` to handle all failure modes including defects.
Evidence trail:
1. apps/server/src/wsServer.ts lines 787-791: `Effect.catch((error) => cleanupCreatedThread().pipe(...))` - the code under review
2. apps/server/src/wsServer.ts lines 740-785: `bootstrapProgram` definition showing calls to `git.createWorktree()` and other operations that could throw defects
3. apps/server/src/wsServer.ts lines 675-685: `cleanupCreatedThread()` function definition showing it uses `Effect.ignoreCause({ log: true })` but depends on being called
4. https://github.com/PaulJPhilp/EffectPatterns - Effect Patterns Hub documents 'Handle Unexpected Errors by Inspecting the Cause | Use Effect.catchAllCause or Effect.runFork to inspect the Cause of a failure, distinguishing between expected errors (Fail) and unexpected defects (Die)'
5. Effect library version: effect 4.0.0-beta.42 (package.json in root)
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Terminal event handler opens drawer for all terminal events
- Guarded the handler to only fire for setup terminals (IDs starting with 'setup-') and simplified worktreePath to always use event.snapshot.cwd since non-setup terminals are now excluded.
Or push these changes by commenting:
@cursor push b28aa51f71
Preview (b28aa51f71)
diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx
--- a/apps/web/src/routes/__root.tsx
+++ b/apps/web/src/routes/__root.tsx
@@ -223,12 +223,15 @@
domainEventFlushThrottler.maybeExecute();
});
const unsubTerminalEvent = api.terminal.onEvent((event) => {
- if (event.type === "started" || event.type === "restarted") {
+ if (
+ (event.type === "started" || event.type === "restarted") &&
+ event.terminalId.startsWith("setup-")
+ ) {
const threadId = ThreadId.makeUnsafe(event.threadId);
ensureTerminal(threadId, event.terminalId, { open: true, active: true });
setTerminalLaunchContext(threadId, {
cwd: event.snapshot.cwd,
- worktreePath: event.terminalId.startsWith("setup-") ? event.snapshot.cwd : null,
+ worktreePath: event.snapshot.cwd,
});
}
const hasRunningSubprocess = terminalRunningSubprocessFromEvent(event);You can send follow-ups to this agent here.
Co-authored-by: codex <codex@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Preparing-worktree indicator set and immediately reset
- Moved
beginLocalDispatch({ preparingWorktree: false })from beforedispatchCommandto after it, so the preparing-worktree indicator stays true during the server-side worktree creation.
- Moved
Or push these changes by commenting:
@cursor push c62a5d68cc
Preview (c62a5d68cc)
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -2803,7 +2803,6 @@
});
}
- beginLocalDispatch({ preparingWorktree: false });
const turnAttachments = await turnAttachmentsPromise;
const bootstrap =
isLocalDraftThread || baseBranchForWorktree
@@ -2851,6 +2850,7 @@
...(bootstrap ? { bootstrap } : {}),
createdAt: messageCreatedAt,
});
+ beginLocalDispatch({ preparingWorktree: false });
turnStartSucceeded = true;
})().catch(async (err: unknown) => {
if (You can send follow-ups to this agent here.
…renders during server-side worktree creation Applied via @cursor push command


Summary
Note
Medium Risk
Moderate risk because it changes the
thread.turn.startcommand flow to optionally create threads/worktrees and run setup scripts server-side, plus adds new terminal state/launch-context handling that could affect terminal routing and onboarding flows.Overview
Moves worktree/thread bootstrap off the client and into the server.
thread.turn.startnow supports an optionalbootstrappayload so the server can (optionally) create the thread, create a git worktree, update thread metadata, and then dispatch the actual turn.Introduces server-driven setup-script execution. Adds a new
ProjectSetupScriptRunnerservice/layer that opens a deterministic setup terminal withT3CODE_PROJECT_ROOT/T3CODE_WORKTREE_PATHenv and writes the setup command;GitManager.preparePullRequestThreadcan now trigger it for new PR worktrees when athreadIdis provided (failures are logged and do not block worktree creation).Improves terminal UX by persisting launch context. The web app adds
terminalLaunchContextByThreadIdand anensureTerminalaction; terminal start/restart events seed launch context so the terminal drawer uses the correct cwd/worktree context during bootstrap/setup, and client-side PR/worktree setup execution is removed in favor of server ownership.Contracts and tests updated accordingly. Adds schema support for
bootstraponthread.turn.start, extendsgit.preparePullRequestThreadinput with optionalthreadId, shares project-script helpers via@t3tools/shared/projectScripts, and adds coverage for PR worktree setup, bootstrap turn-starts, and terminal context behavior.Written by Cursor Bugbot for commit e3372a4. This will update automatically on new commits. Configure here.
Note
Move worktree bootstrap and setup script execution to the server on thread turn start
thread.turn.startcommands can carry an optionalbootstrappayload (createThread,prepareWorktree,runSetupScript) handled by the server in wsServer.ts.ProjectSetupScriptRunnerservice (ProjectSetupScriptRunner.ts) opens a terminal with the correct working directory and environment variables and writes the setup command, returning structured status metadata.GitManager.preparePullRequestThreadnow invokesProjectSetupScriptRunner.runForThreadafter creating a new worktree when athreadIdis provided; failures are swallowed with a warning and do not fail the operation.ensureTerminal,setTerminalLaunchContext, andclearTerminalLaunchContextto track per-thread terminal launch context; the UI derives terminal drawercwdandworktreePathfrom this context.projectScriptCwd,projectScriptRuntimeEnv,setupProjectScript) are extracted topackages/sharedand reused by both client and server.dispatchBootstrapTurnStartattempts thread cleanup on failure, but partial state (e.g. a created worktree without a completed turn) may persist if cleanup itself fails.Macroscope summarized e3372a4.